#include "PNGImage.h"
#include "png.h"

//libpng is MUCH easier to use with standard C functions
#include <cstdlib>
#include <cstdio>
//use for cerr stream
#include <iostream>
using namespace std;

PNGImage::PNGImage()
{
	//initialize fields
	text_ptr = NULL;
	num_text = -1;
	initialized=false;
}

PNGImage::~PNGImage()
{
	if(text_ptr != NULL)
	{
		delete[] text_ptr;
	}
	
	png_destroy_read_struct(&PNG_PTR, &INFO_PTR, (png_infopp)NULL);
}

bool PNGImage::loadPNGFromFile(string* filename)
{
	FILE *fp = fopen(filename->c_str(), "rb");
	if(!fp)
	{
		//HANDLE ERROR
		cerr << "COULD NOT OPEN FILE: " << filename << endl;
		return false;
	}
	//initialize memory
	int numberToRead = 8;
	char header[sizeof(char)*numberToRead];
	//read the header & pass to pnglib
	fread(header, sizeof(char), numberToRead, fp);
	bool is_png = !png_sig_cmp((png_byte*)header, 0, numberToRead);
	if(!is_png)
	{
		cerr << "FILE: " << filename << " IS NOT A PNG IMAGE" << endl;
		return false;
	}
	
	//TODO:  add error handling routines
	png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
								NULL, NULL, NULL);
	
	//could not read PNG image, handle error
	if(!png_ptr)
	{
		cerr << "ERROR CREATING PNG POINTERS!" << endl;
		handlePNGError();
		return false;
	}
	
	png_infop info_ptr = png_create_info_struct(png_ptr);
	
	//handle error
	if(!info_ptr)
	{
		cerr << "ERROR CREATING INFO POINTER!" << endl;
		png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
		handlePNGError();
		return false;
	}
	
	//now the input code
	png_init_io(png_ptr, fp);
	
	//the number of signature bytes read
	png_set_sig_bytes(png_ptr, numberToRead);
	
	//TODO:  SET UP CALLBACK FOR handleUnknownChunk
	
	//read the entire image into memory with no transformations
	png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
	
	//allocate a struct to hold the image data
	//first, get the image height
	//int height = png_get_image_height(png_ptr, info_ptr);
	//and allocate
	//FIXME:  IS THIS RIGHT?
	//png_bytepp rowPointers;
	
	//rowPointers = png_get_rows(png_ptr, info_ptr);
	
	//png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
	
	//do all assignments
	//row_pointers = rowPointers;
	INFO_PTR=info_ptr;
	PNG_PTR =png_ptr;
	
	return true;
}

bool PNGImage::initializePNGModel(string* filename,
									TextChunk* textChunk)
{
	if(!loadPNGFromFile(filename))
	{
		//HANDLE ERROR
		cerr << "COULD NOT LOAD PNG FROM FILE!" << endl; 
		return false;
	}
	if(!setTextFieldsWithTextChunk(textChunk))
	{
		cerr << "COULD NOT SET TEXT FIELDS WITH TEXT CHUNK!" << endl;
		return false;
	}
	initialized=true;
	return true;
}

bool PNGImage::writeNewImageWithTextChunk(string* newfilename)
{
	if(initialized == false)
	{
		cerr << "HAVE NOT BEEN INITIALIZED!" << endl;
		return false;
	}
	if(text_ptr == NULL)
	{
		cerr << "text_ptr IS NULL!" << endl;
		return false;
	}
	if(num_text < 0)
	{
		cerr << "num_text IS LESS THAN 0!" << endl;
		return false;
	}
	
	FILE *fp = fopen(newfilename->c_str(), "wb");
	//handle error
	if(!fp)
	{
		cerr << "ERROR:  COULD NOT OPEN FILE FOR WRITING" << endl;
		return false;
	}
	
	//TODO:  ASSIGN FUNCTIONS
	png_structp png_ptr = png_create_write_struct
							(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	
	png_infop infostruct = png_create_info_struct(png_ptr);
	
	if (!png_ptr)
	{
		cerr << "ERROR CREATING PNG WRITE STRUCT" << endl;
		fclose(fp);
		return false;
	}
	
	/*png_infop info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr)
	{
		cerr << "ERROR CREATING PNG INFO STRUCT" << endl;
		png_destroy_write_struct(&png_ptr,
									(png_infopp)NULL);
		close(fp);
		return false;
	}*/
	
	//begin IO
	png_init_io(png_ptr, fp);
	

	//set the text data
	png_set_text(png_ptr, infostruct, text_ptr, num_text);
	
	//write the file
	png_write_png(png_ptr, infostruct, PNG_TRANSFORM_IDENTITY, NULL);
	
	png_destroy_write_struct(&png_ptr, &infostruct);
}

void PNGImage::handlePNGError()
{
	
}

int PNGImage::handleUnknownChunk(png_structp png_ptr, 
										png_unknown_chunkp chunk)
{
	png_byte name[5];
	png_byte *data;
	png_size_t size;
	//FIXME:  THIS WILL GIVE A SIGSEV- LOOK UP HOW
	//TO READ FROM png_unknown_chunkp
	cerr << "UNKNOWN CHUNK ENCOUNTERED" << endl;
	cerr << "Name:  " << (char*)name << endl;
	
	return (-1); /* chunk had an error */
	return (0); /* did not recognize */
	return (1); /* success */
}

bool PNGImage::setTextFieldsWithTextChunk(TextChunk* textChunk)
{
	string** internalStringArray = textChunk->getAllStrings();
	if(internalStringArray == NULL)
	{
		cerr << "internalStringArray is NULL!" << endl;
		return false;
	}
	
	//9 keywords
	num_text = 9;
	text_ptr = new png_text[num_text];
	
	//loop through and set parameters that will be consistent through
	//the whole array...
	for(int i = 0; i < num_text; i++)
	{
		//no compression
		text_ptr[i].compression = PNG_TEXT_COMPRESSION_NONE;
		
		//not using itxt chunks
		text_ptr[i].itxt_length = 0;
		
		//NULL for both of these
		text_ptr[i].lang = NULL;
		
		text_ptr[i].text        = (char*)internalStringArray[i][0].c_str();
		text_ptr[i].text_length =        internalStringArray[i][0].size();
	}
	
	string keys[] = { "Title", "Author", "Description", "Copyright",
					  "Creation Time", "Software", "Disclaimer",
					  "Warning", "Source"
	};
	
	//now the manual settings
	text_ptr[0].key = (char*)keys[0].c_str();
	text_ptr[1].key = (char*)keys[1].c_str();
	text_ptr[2].key = (char*)keys[2].c_str();
	text_ptr[3].key = (char*)keys[3].c_str();
	text_ptr[4].key = (char*)keys[4].c_str();
	text_ptr[5].key = (char*)keys[5].c_str();
	text_ptr[6].key = (char*)keys[6].c_str();
	text_ptr[7].key = (char*)keys[7].c_str();
	text_ptr[8].key = (char*)keys[8].c_str();
	
	return true;
}